package jmine.tec.utils.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author takeshi
 */
public class ReversibleOutputStream extends OutputStream {

    public static final int DEFAULT_LIMIT = 10 * 1024;

    private int limit;

    private ByteArrayOutputStream bout;

    private OutputStream delegate;

    private File file;

    /**
     * C'tor que usa {@value #DEFAULT_LIMIT} como tamanho limite
     */
    public ReversibleOutputStream() {
        this(DEFAULT_LIMIT);
    }

    /**
     * C'tor
     * 
     * @param limit int
     */
    public ReversibleOutputStream(int limit) {
        this.limit = limit;
        this.bout = new ByteArrayOutputStream(limit);
        this.delegate = this.bout;
    }

    /**
     * Quando o limite eh alcancado, este metodo eh chamado para alternar o backing store de memoria para arquivo
     * 
     * @throws IOException e
     */
    private void changeDelegateToFile() throws IOException {
        if (this.bout == null) {// nothing to do
            return;
        }
        this.file = File.createTempFile("temporaryStream", "bin");
        this.file.deleteOnExit();
        this.delegate = new FileOutputStream(this.file);
        this.delegate.write(this.bout.toByteArray());
        this.bout = null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void write(byte[] b, int off, int len) throws IOException {
        if (delegate == null) {
            throw new IOException("Stream already closed");
        }
        if (this.limit < len) {
            this.changeDelegateToFile();
        }
        delegate.write(b, off, len);
        this.limit -= len;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void write(int b) throws IOException {
        if (delegate == null) {
            throw new IOException("Stream already closed");
        }
        if (this.limit == 0) {
            this.changeDelegateToFile();
        }
        this.delegate.write(b);
        this.limit--;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void flush() throws IOException {
        this.delegate.flush();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() throws IOException {
        if (delegate != null) {
            this.delegate.flush();
            this.delegate.close();
        }
        this.delegate = null;
    }

    /**
     * Fecha a stream e devolve um {@link InputStream} com o conteudo gravado
     * 
     * @return {@link InputStream}
     * @throws IOException e
     */
    public synchronized InputStream reverse() throws IOException {
        this.close();
        if (this.bout != null) {
            return new ByteArrayInputStream(this.bout.toByteArray());
        }
        return new TemporaryFileInputStream(this.file);
    }

}
